This is an R Markdown Notebook. When you execute code within the notebook, the results appear beneath the code.

Try executing this chunk by clicking the Run button within the chunk or by placing your cursor inside it and pressing Ctrl+Shift+Enter.

library(xgboost)
library(Matrix)
library(mclust)
    __  ___________    __  _____________
   /  |/  / ____/ /   / / / / ___/_  __/
  / /|_/ / /   / /   / / / /\__ \ / /   
 / /  / / /___/ /___/ /_/ /___/ // /    
/_/  /_/\____/_____/\____//____//_/    version 5.4.9
Type 'citation("mclust")' for citing this R package in publications.
library(tidyverse)
Registered S3 methods overwritten by 'dbplyr':
  method         from
  print.tbl_lazy     
  print.tbl_sql      
─ Attaching packages ─────────────────────────────────── tidyverse 1.3.1 ─
✓ tibble  3.1.5     ✓ stringr 1.4.0
✓ readr   2.0.2     ✓ forcats 0.5.1
✓ purrr   0.3.4     
─ Conflicts ───────────────────────────────────── tidyverse_conflicts() ─
x dplyr::collapse()   masks IRanges::collapse()
x dplyr::combine()    masks Biobase::combine(), BiocGenerics::combine()
x dplyr::count()      masks matrixStats::count()
x dplyr::desc()       masks IRanges::desc()
x tidyr::expand()     masks Matrix::expand(), S4Vectors::expand()
x plotly::filter()    masks dplyr::filter(), stats::filter()
x dplyr::first()      masks S4Vectors::first()
x widgetTools::funs() masks dplyr::funs()
x dplyr::lag()        masks stats::lag()
x purrr::map()        masks mclust::map()
x tidyr::pack()       masks Matrix::pack()
x ggplot2::Position() masks BiocGenerics::Position(), base::Position()
x purrr::reduce()     masks GenomicRanges::reduce(), IRanges::reduce()
x plotly::rename()    masks dplyr::rename(), S4Vectors::rename()
x purrr::simplify()   masks clusterProfiler::simplify(), DelayedArray::simplify()
x plotly::slice()     masks dplyr::slice(), xgboost::slice(), IRanges::slice()
x tidyr::unpack()     masks Matrix::unpack()

数值化

ds2训练分类器


ds2_data <- get_data_table(ds2, highvar = F, type = "data")
ds2_label <- as.numeric(as.character(Idents(ds2)))

index <- c(1:dim(ds2_data)[2]) %>% sample(ceiling(0.3*dim(ds2_data)[2]), replace = F, prob = NULL)
colnames(ds2_data) <- NULL

ds2_train_data <- list(data = t(as(ds2_data[,-index],"dgCMatrix")), label = ds2_label[-index])
ds2_test_data <- list(data = t(as(ds2_data[,index],"dgCMatrix")), label = ds2_label[index])

ds2_train <- xgb.DMatrix(data = ds2_train_data$data,label = ds2_train_data$label)
ds2_test <- xgb.DMatrix(data = ds2_test_data$data,label = ds2_test_data$label)

watchlist <- list(train = ds2_train, eval = ds2_test)
xgb_param <- list(eta = 0.2, max_depth = 6, 
                  subsample = 0.6,  num_class = length(table(Idents(ds2))),
                  objective = "multi:softprob", eval_metric = 'mlogloss')

bst_model <- xgb.train(xgb_param, ds2_train, nrounds = 100, watchlist, verbose = 0)

eval_loss <- bst_model[["evaluation_log"]][["eval_mlogloss"]]
plot_ly(data.frame(eval_loss), x = c(1:100), y = eval_loss) %>% 
  add_trace(type = "scatter", mode = "markers+lines", 
            marker = list(color = "black", line = list(color = "#1E90FFC7", width = 1)),
            line = list(color = "#1E90FF80", width = 2)) %>% 
  layout(xaxis = list(title = "epoch"),yaxis = list(title = "eval_mlogloss"))

ds2 -> ds1

Idents(ds1) <- ds1$seurat_clusters
temp <- get_data_table(ds1, highvar = F, type = "data")
ds1_data <- matrix(data=0, nrow = length(rownames(ds2_data)), ncol = length(colnames(temp)), 
                   byrow = FALSE, dimnames = list(rownames(ds2_data),colnames(temp)))
for(i in intersect(rownames(ds2_data), rownames(temp))){
  ds1_data[i,] <- temp[i,]
}
rm(temp)
ds1_label <- as.numeric(as.character(Idents(ds1)))
colnames(ds1_data) <- NULL
ds1_test_data <- list(data = t(as(ds1_data,"dgCMatrix")), label = ds1_label)
ds1_test <- xgb.DMatrix(data = ds1_test_data$data,label = ds1_test_data$label)

#预测结果

predict_ds1_test <- predict(bst_model, newdata = ds1_test)

predict_prop_ds1 <- matrix(data=predict_ds1_test, nrow = length(levels(Idents(ds2))), 
                           ncol = ncol(ds1), byrow = FALSE, 
                           dimnames = list(levels(Idents(ds2)),colnames(ds1)))

## 得到分群结果
ds1_res <- apply(predict_prop_ds1,2,func,rownames(predict_prop_ds1))
Idents(ds1) <- factor(ds1_res,levels = c(0:4))
umapplot(ds1)

ds1$supclustering <- Idents(ds1) #保存监督聚类结果

数值化地投射回umap

embedding <- FetchData(object = ds1, vars = c("UMAP_1", "UMAP_2"))
embedding <- cbind(embedding, t(predict_prop_ds1))

ggobj <- ggplot() +
  geom_point(data = embedding[embedding$`0`>0.1,], 
             aes(x = UMAP_1, y = UMAP_2, color = `0`), shape=16, size = 3, alpha=0.5) + 
  scale_color_gradient('0', low = "#FFFFFF00", high = "#6dc0a6") +
  new_scale("color") +
    geom_point(data = embedding[embedding$`1`>0.1,], 
             aes(x = UMAP_1, y = UMAP_2, color = `1`), size = 3, alpha=0.5) + 
  scale_color_gradient('1', low = "#FFFFFF00", high = "#e2b398") +
   new_scale("color") +
    geom_point(data = embedding[embedding$`2`>0.1,], 
             aes(x = UMAP_1, y = UMAP_2, color = `2`), size = 2, alpha=0.5) + 
  scale_color_gradient('2', low = "#FFFFFF00", high = "#e2a2ca") +
  new_scale("color") +
    geom_point(data = embedding[embedding$`3`>0.1,], 
             aes(x = UMAP_1, y = UMAP_2, color = `3`), size = 3, alpha=0.5) + 
  scale_color_gradient('3', low = "#FFFFFF00", high = "#d1eba8") +
   new_scale("color") +
      geom_point(data = embedding[embedding$`4`>0.1,], 
             aes(x = UMAP_1, y = UMAP_2, color = `4`), size = 3, alpha=0.5) + 
  scale_color_gradient('4', low = "#FFFFFF00", high = "#b1d6fb") +
    new_scale("color") +
        xlab("UMAP 1") + ylab("UMAP 2")  +
        theme(axis.line = element_line(arrow = arrow(length = unit(0.2, "cm")))) +
        scale_y_continuous(breaks = NULL) +
        scale_x_continuous(breaks = NULL) + 
  theme(panel.background = element_blank(), panel.grid = element_blank(), legend.position = "bottom")
ggsave("pre_ds1_umap.svg",device = svg,plot = ggobj,height = 10,width = 10)

#ds2 -> ds0

embedding <- FetchData(object = ds0, vars = c("UMAP_1", "UMAP_2"))
embedding <- cbind(embedding, t(predict_prop_ds0))

ggobj <- ggplot() +
  geom_point(data = embedding[embedding$`0`>0.1,], 
             aes(x = UMAP_1, y = UMAP_2, color = `0`), shape=16, size = 3, alpha=0.5) + 
  scale_color_gradient('0', low = "#FFFFFF00", high = "#6dc0a6") +
  new_scale("color") +
    geom_point(data = embedding[embedding$`1`>0.1,], 
             aes(x = UMAP_1, y = UMAP_2, color = `1`),shape=16, size = 3, alpha=0.5) + 
  scale_color_gradient('1', low = "#FFFFFF00", high = "#e2b398") +
   new_scale("color") +
    geom_point(data = embedding[embedding$`2`>0.1,], 
             aes(x = UMAP_1, y = UMAP_2, color = `2`),shape=16, size = 2, alpha=0.5) + 
  scale_color_gradient('2', low = "#FFFFFF00", high = "#e2a2ca") +
  new_scale("color") +
    geom_point(data = embedding[embedding$`3`>0.1,], 
             aes(x = UMAP_1, y = UMAP_2, color = `3`),shape=16, size = 3, alpha=0.5) + 
  scale_color_gradient('3', low = "#FFFFFF00", high = "#d1eba8") +
   new_scale("color") +
      geom_point(data = embedding[embedding$`4`>0.1,], 
             aes(x = UMAP_1, y = UMAP_2, color = `4`),shape=16, size = 3, alpha=0.5) + 
  scale_color_gradient('4', low = "#FFFFFF00", high = "#b1d6fb") +
    new_scale("color") +
        xlab("UMAP 1") + ylab("UMAP 2")  +
        theme(axis.line = element_line(arrow = arrow(length = unit(0.2, "cm")))) +
        scale_y_continuous(breaks = NULL) +
        scale_x_continuous(breaks = NULL) + 
  theme(panel.background = element_blank(), panel.grid = element_blank(), legend.position = "bottom")
ggsave("pre_ds1_umap.svg",device = svg,plot = ggobj,height = 10,width = 10)

PA -> AC

Idents(ds2_PA) <- ds2_PA$seurat_clusters
selected_features <- read.csv("./datatable/selected_features.csv", stringsAsFactors = F)
selected_features <- selected_features$x
PA_data <- get_data_table(ds2_PA, highvar = F, type = "data")
PA_data <- PA_data[selected_features,]
PA_label <- as.numeric(as.character(Idents(ds2_PA)))
colnames(PA_data) <- NULL

PA_train_data <- list(data = t(as(PA_data,"dgCMatrix")), label = PA_label)
PA_train <- xgb.DMatrix(data = PA_train_data$data,label = PA_train_data$label)
xgb_param <- list(eta = 0.2, max_depth = 6, 
                  subsample = 0.6,  num_class = length(table(Idents(ds2_PA))),
                  objective = "multi:softprob", eval_metric = 'mlogloss')

bst_model <- xgb.train(xgb_param, PA_train, nrounds = 100, verbose = 0)
embedding <- FetchData(object = ds2_AC, vars = c("UMAP_1", "UMAP_2"))
embedding <- cbind(embedding, t(predict_prop_AC))

ggobj <- ggplot() +
  geom_point(data = embedding[embedding$`0`>0.1,], 
             aes(x = UMAP_1, y = UMAP_2, color = `0`), shape=16, size = 2, alpha=0.5) + 
  scale_color_gradient('0', low = "#FFFFFF00", high = "#6dc0a6") +
  new_scale("color") +
    geom_point(data = embedding[embedding$`1`>0.1,], 
             aes(x = UMAP_1, y = UMAP_2, color = `1`),shape=16, size = 2, alpha=0.5) + 
  scale_color_gradient('1', low = "#FFFFFF00", high = "#e2b398") +
   new_scale("color") +
    geom_point(data = embedding[embedding$`2`>0.1,], 
             aes(x = UMAP_1, y = UMAP_2, color = `2`),shape=16, size = 2, alpha=0.5) + 
  scale_color_gradient('2', low = "#FFFFFF00", high = "#e2a2ca") +
        xlab("UMAP 1") + ylab("UMAP 2")  +
        theme(axis.line = element_line(arrow = arrow(length = unit(0.2, "cm")))) +
        scale_y_continuous(breaks = NULL) +
        scale_x_continuous(breaks = NULL) + 
  theme(panel.background = element_blank(), panel.grid = element_blank(), legend.position = "bottom")
ggsave("ds2_PAtoAC_umap.svg",device = svg,plot = ggobj,height = 8,width = 8)

AC to PA

Idents(ds2_AC) <- ds2_AC$seurat_clusters
selected_features <- read.csv("./datatable/selected_features.csv", stringsAsFactors = F)
selected_features <- selected_features$x
AC_data <- get_data_table(ds2_AC, highvar = F, type = "data")
AC_data <- AC_data[selected_features,]
AC_label <- as.numeric(as.character(Idents(ds2_AC)))
colnames(AC_data) <- NULL

AC_train_data <- list(data = t(as(AC_data,"dgCMatrix")), label = AC_label)
AC_train <- xgb.DMatrix(data = AC_train_data$data,label = AC_train_data$label)
xgb_ACram <- list(eta = 0.2, max_depth = 6, 
                  subsample = 0.6,  num_class = length(table(Idents(ds2_AC))),
                  objective = "multi:softprob", eval_metric = 'mlogloss')

bst_model <- xgb.train(xgb_ACram, AC_train, nrounds = 100, verbose = 0)
Idents(ds2_PA) <- factor(ds2_PA$seurat_clusters,levels = c(0,1,2))

PA_data <- get_data_table(ds2_PA, highvar = F, type = "data")
PA_data <- PA_data[selected_features,]
PA_label <- as.numeric(as.character(Idents(ds2_PA)))
colnames(PA_data) <- NULL
PA_test_data <- list(data = t(as(PA_data,"dgCMatrix")), label = PA_label)
PA_test <- xgb.DMatrix(data = PA_test_data$data,label = PA_test_data$label)

#预测结果
predict_prop_PA <-predict(bst_model, newdata = PA_test) %>%
 matrix(nrow = length(levels(Idents(ds2_AC))), 
                           ncol = ncol(ds2_PA), byrow = FALSE, 
                           dimnames = list(levels(Idents(ds2_AC)),colnames(ds2_PA)))
PA_res <- apply(predict_prop_PA,2,func,rownames(predict_prop_PA))

confuse_matrix1 <- table(PA_test_data$label, PA_res, dnn=c("true","pre"))
sankey_plot(confuse_matrix1,session = "ACtoPA")

Idents(ds2_PA) <- factor(PA_res)
umapplot(ds2_PA)

embedding <- FetchData(object = ds2_PA, vars = c("UMAP_1", "UMAP_2"))
embedding <- cbind(embedding, t(predict_prop_PA))

ggobj <- ggplot() +
  geom_point(data = embedding[embedding$`0`>0.1,], 
             aes(x = UMAP_1, y = UMAP_2, color = `0`), shape=16, size = 2, alpha=0.5) + 
  scale_color_gradient('0', low = "#FFFFFF00", high = "#6dc0a6") +
  new_scale("color") +
    geom_point(data = embedding[embedding$`1`>0.1,], 
             aes(x = UMAP_1, y = UMAP_2, color = `1`),shape=16, size = 2, alpha=0.5) + 
  scale_color_gradient('1', low = "#FFFFFF00", high = "#e2b398") +
   new_scale("color") +
    geom_point(data = embedding[embedding$`2`>0.1,], 
             aes(x = UMAP_1, y = UMAP_2, color = `2`),shape=16, size = 2, alpha=0.5) + 
  scale_color_gradient('2', low = "#FFFFFF00", high = "#e2a2ca") +
     new_scale("color") +
    geom_point(data = embedding[embedding$`3`>0.1,], 
             aes(x = UMAP_1, y = UMAP_2, color = `3`),shape=16, size = 2, alpha=0.5) + 
  scale_color_gradient('3', low = "#FFFFFF00", high = "#d1eba8") +
        xlab("UMAP 1") + ylab("UMAP 2")  +
        theme(axis.line = element_line(arrow = arrow(length = unit(0.2, "cm")))) +
        scale_y_continuous(breaks = NULL) +
        scale_x_continuous(breaks = NULL) + 
  theme(panel.background = element_blank(), panel.grid = element_blank(), legend.position = "bottom")
ggsave("ds2_ACtoPA_umap.svg",device = svg,plot = ggobj,height = 8,width = 8)

在ds0上训练

Idents(ds0) <- ds0$seurat_clusters
ds0_data <- get_data_table(ds0, highvar = F, type = "data")
ds0_label <- as.numeric(as.character(Idents(ds0)))

index <- c(1:dim(ds0_data)[2]) %>% sample(ceiling(0.3*dim(ds0_data)[2]), replace = F, prob = NULL)
colnames(ds0_data) <- NULL

ds0_train_data <- list(data = t(as(ds0_data[,-index],"dgCMatrix")), label = ds0_label[-index])
ds0_test_data <- list(data = t(as(ds0_data[,index],"dgCMatrix")), label = ds0_label[index])

ds0_train <- xgb.DMatrix(data = ds0_train_data$data,label = ds0_train_data$label)
ds0_test <- xgb.DMatrix(data = ds0_test_data$data,label = ds0_test_data$label)

watchlist <- list(train = ds0_train, eval = ds0_test)
xgb_param <- list(eta = 0.2, max_depth = 6, 
                  subsample = 0.6,  num_class = length(table(Idents(ds0))),
                  objective = "multi:softprob", eval_metric = 'mlogloss')

bst_model <- xgb.train(xgb_param, ds0_train, nrounds = 100, watchlist, verbose = 0)

eval_loss <- bst_model[["evaluation_log"]][["eval_mlogloss"]]
plot_ly(data.frame(eval_loss), x = c(1:100), y = eval_loss) %>% 
  add_trace(type = "scatter", mode = "markers+lines", 
            marker = list(color = "black", line = list(color = "#1E90FFC7", width = 1)),
            line = list(color = "#1E90FF80", width = 2)) %>% 
  layout(xaxis = list(title = "epoch"),yaxis = list(title = "eval_mlogloss"))
importance <- xgb.importance(colnames(ds0_train), model = bst_model)
head(importance)
xgb.ggplot.importance(head(importance,20),n_clusters = 1) + theme_bw()+theme(
    axis.title.x = element_text(size = 15), axis.text.x = element_text(size = 8, colour = "black"),
    axis.title.y = element_text(size = 15), axis.text.y = element_text(size = 12, colour = "black"),
    legend.text = element_text(size = 20), legend.title = element_blank(), panel.grid = element_blank())

write.csv(importance, "./datatable/ds0_features.csv", row.names = F)
multi_featureplot(head(importance,9)$Feature, ds0, labels = "") 
Warning: Using `as.character()` on a quosure is deprecated as of rlang 0.3.0.
Please use `as_label()` or `as_name()` instead.
This warning is displayed once per session.

ds0 -> ds2

Idents(ds2) <- ds2$seurat_clusters 
temp <- get_data_table(ds2, highvar = F, type = "data")
ds2_data <- matrix(data=0, nrow = length(rownames(ds0_data)), ncol = length(colnames(temp)), 
                   byrow = FALSE, dimnames = list(rownames(ds0_data),colnames(temp)))
for(i in intersect(rownames(ds2_data), rownames(temp))){
  ds2_data[i,] <- temp[i,]
}
rm(temp)
ds2_label <- as.numeric(as.character(Idents(ds2)))
colnames(ds2_data) <- NULL
ds2_test_data <- list(data = t(as(ds2_data,"dgCMatrix")), label = ds2_label)
ds2_test <- xgb.DMatrix(data = ds2_test_data$data,label = ds2_test_data$label)

#预测结果

predict_ds2_test <- predict(bst_model, newdata = ds2_test)

predict_prop_ds2 <- matrix(data=predict_ds2_test, nrow = bst_model[["params"]][["num_class"]], 
                           ncol = ncol(ds2), byrow = FALSE, 
                           dimnames = list(c(0:(bst_model[["params"]][["num_class"]]-1)),colnames(ds2)))

## 得到分群结果
ds2_res <- apply(predict_prop_ds2,2,func,rownames(predict_prop_ds2))
confuse_matrix1 <- table(ds2_test_data$label, ds2_res, dnn=c("true","pre"))

sankey_plot(confuse_matrix1,0:5,0:4,session = "ds0tods2")

Idents(ds2) <- factor(ds2_res,levels = c(0:5))
umapplot(ds2)

embedding <- FetchData(object = ds2, vars = c("UMAP_1", "UMAP_2"))
embedding <- cbind(embedding, t(predict_prop_ds2))

ggobj <- ggplot() +
  geom_point(data = embedding[embedding$`0`>0.1,], 
             aes(x = UMAP_1, y = UMAP_2, color = `0`), shape=16, size = 2, alpha=0.5) + 
  scale_color_gradient('0', low = "#FFFFFF00", high = "#6dc0a6") +
  new_scale("color") +
    geom_point(data = embedding[embedding$`1`>0.1,], 
             aes(x = UMAP_1, y = UMAP_2, color = `1`),shape=16, size = 2, alpha=0.5) + 
  scale_color_gradient('1', low = "#FFFFFF00", high = "#e2b398") +
   new_scale("color") +
    geom_point(data = embedding[embedding$`2`>0.1,], 
             aes(x = UMAP_1, y = UMAP_2, color = `2`),shape=16, size = 2, alpha=0.5) + 
  scale_color_gradient('2', low = "#FFFFFF00", high = "#e2a2ca") +
     new_scale("color") +
    geom_point(data = embedding[embedding$`3`>0.1,], 
             aes(x = UMAP_1, y = UMAP_2, color = `3`),shape=16, size = 2, alpha=0.5) + 
  scale_color_gradient('3', low = "#FFFFFF00", high = "#d1eba8") +
     new_scale("color") +
    geom_point(data = embedding[embedding$`4`>0.1,], 
             aes(x = UMAP_1, y = UMAP_2, color = `4`),shape=16, size = 2, alpha=0.5) + 
  scale_color_gradient('4', low = "#FFFFFF00", high = "#b1d6fb") +
     new_scale("color") +
    geom_point(data = embedding[embedding$`5`>0.1,], 
             aes(x = UMAP_1, y = UMAP_2, color = `5`),shape=16, size = 2, alpha=0.5) + 
  scale_color_gradient('5', low = "#FFFFFF00", high = "#fd9999") +
        xlab("UMAP 1") + ylab("UMAP 2")  +
        theme(axis.line = element_line(arrow = arrow(length = unit(0.2, "cm")))) +
        scale_y_continuous(breaks = NULL) +
        scale_x_continuous(breaks = NULL) + 
  theme(panel.background = element_blank(), panel.grid = element_blank(), legend.position = "bottom")
ggsave("ds0tods2umap.svg",device = svg,plot = ggobj,height = 8,width = 8)

ds0 -> ds1

embedding <- FetchData(object = ds1, vars = c("UMAP_1", "UMAP_2"))
embedding <- cbind(embedding, t(predict_prop_ds1))

ggobj <- ggplot() +
  geom_point(data = embedding[embedding$`0`>0.1,], 
             aes(x = UMAP_1, y = UMAP_2, color = `0`), shape=16, size = 2, alpha=0.5) + 
  scale_color_gradient('0', low = "#FFFFFF00", high = "#6dc0a6") +
  new_scale("color") +
    geom_point(data = embedding[embedding$`1`>0.1,], 
             aes(x = UMAP_1, y = UMAP_2, color = `1`),shape=16, size = 2, alpha=0.5) + 
  scale_color_gradient('1', low = "#FFFFFF00", high = "#e2b398") +
   new_scale("color") +
    geom_point(data = embedding[embedding$`2`>0.1,], 
             aes(x = UMAP_1, y = UMAP_2, color = `2`),shape=16, size = 2, alpha=0.5) + 
  scale_color_gradient('2', low = "#FFFFFF00", high = "#e2a2ca") +
     new_scale("color") +
    geom_point(data = embedding[embedding$`3`>0.1,], 
             aes(x = UMAP_1, y = UMAP_2, color = `3`),shape=16, size = 2, alpha=0.5) + 
  scale_color_gradient('3', low = "#FFFFFF00", high = "#d1eba8") +
     new_scale("color") +
    geom_point(data = embedding[embedding$`4`>0.1,], 
             aes(x = UMAP_1, y = UMAP_2, color = `4`),shape=16, size = 2, alpha=0.5) + 
  scale_color_gradient('4', low = "#FFFFFF00", high = "#b1d6fb") +
     new_scale("color") +
    geom_point(data = embedding[embedding$`5`>0.1,], 
             aes(x = UMAP_1, y = UMAP_2, color = `5`),shape=16, size = 2, alpha=0.5) + 
  scale_color_gradient('5', low = "#FFFFFF00", high = "#fd9999") +
        xlab("UMAP 1") + ylab("UMAP 2")  +
        theme(axis.line = element_line(arrow = arrow(length = unit(0.2, "cm")))) +
        scale_y_continuous(breaks = NULL) +
        scale_x_continuous(breaks = NULL) + 
  theme(panel.background = element_blank(), panel.grid = element_blank(), legend.position = "bottom")
ggsave("ds0tods1umap.svg",device = svg,plot = ggobj,height = 8,width = 8)

##lym

ARI 和聚类数的关系

Add a new chunk by clicking the Insert Chunk button on the toolbar or by pressing Ctrl+Alt+I.

When you save the notebook, an HTML file containing the code and output will be saved alongside it (click the Preview button or press Ctrl+Shift+K to preview the HTML file).

The preview shows you a rendered HTML copy of the contents of the editor. Consequently, unlike Knit, Preview does not run any R code chunks. Instead, the output of the chunk when it was last run in the editor is displayed.

LS0tCnRpdGxlOiAiUiBOb3RlYm9vayIKb3V0cHV0OiBodG1sX25vdGVib29rCi0tLQoKVGhpcyBpcyBhbiBbUiBNYXJrZG93bl0oaHR0cDovL3JtYXJrZG93bi5yc3R1ZGlvLmNvbSkgTm90ZWJvb2suIFdoZW4geW91IGV4ZWN1dGUgY29kZSB3aXRoaW4gdGhlIG5vdGVib29rLCB0aGUgcmVzdWx0cyBhcHBlYXIgYmVuZWF0aCB0aGUgY29kZS4gCgpUcnkgZXhlY3V0aW5nIHRoaXMgY2h1bmsgYnkgY2xpY2tpbmcgdGhlICpSdW4qIGJ1dHRvbiB3aXRoaW4gdGhlIGNodW5rIG9yIGJ5IHBsYWNpbmcgeW91ciBjdXJzb3IgaW5zaWRlIGl0IGFuZCBwcmVzc2luZyAqQ3RybCtTaGlmdCtFbnRlciouIAoKYGBge3J9CnNvdXJjZSgidGlhbmZlbmdSd3JhcHBlcnMuUiIpCmxpYnJhcnkoeGdib29zdCkKbGlicmFyeShNYXRyaXgpCmxpYnJhcnkobWNsdXN0KQpsaWJyYXJ5KHRpZHl2ZXJzZSkKYGBgCgoKIyMg5pWw5YC85YyWCiMjIyBkczLorq3nu4PliIbnsbvlmagKYGBge3J9CmRzMl9kYXRhIDwtIGdldF9kYXRhX3RhYmxlKGRzMiwgaGlnaHZhciA9IEYsIHR5cGUgPSAiZGF0YSIpCmRzMl9sYWJlbCA8LSBhcy5udW1lcmljKGFzLmNoYXJhY3RlcihJZGVudHMoZHMyKSkpCgppbmRleCA8LSBjKDE6ZGltKGRzMl9kYXRhKVsyXSkgJT4lIHNhbXBsZShjZWlsaW5nKDAuMypkaW0oZHMyX2RhdGEpWzJdKSwgcmVwbGFjZSA9IEYsIHByb2IgPSBOVUxMKQpjb2xuYW1lcyhkczJfZGF0YSkgPC0gTlVMTAoKZHMyX3RyYWluX2RhdGEgPC0gbGlzdChkYXRhID0gdChhcyhkczJfZGF0YVssLWluZGV4XSwiZGdDTWF0cml4IikpLCBsYWJlbCA9IGRzMl9sYWJlbFstaW5kZXhdKQpkczJfdGVzdF9kYXRhIDwtIGxpc3QoZGF0YSA9IHQoYXMoZHMyX2RhdGFbLGluZGV4XSwiZGdDTWF0cml4IikpLCBsYWJlbCA9IGRzMl9sYWJlbFtpbmRleF0pCgpkczJfdHJhaW4gPC0geGdiLkRNYXRyaXgoZGF0YSA9IGRzMl90cmFpbl9kYXRhJGRhdGEsbGFiZWwgPSBkczJfdHJhaW5fZGF0YSRsYWJlbCkKZHMyX3Rlc3QgPC0geGdiLkRNYXRyaXgoZGF0YSA9IGRzMl90ZXN0X2RhdGEkZGF0YSxsYWJlbCA9IGRzMl90ZXN0X2RhdGEkbGFiZWwpCgp3YXRjaGxpc3QgPC0gbGlzdCh0cmFpbiA9IGRzMl90cmFpbiwgZXZhbCA9IGRzMl90ZXN0KQp4Z2JfcGFyYW0gPC0gbGlzdChldGEgPSAwLjIsIG1heF9kZXB0aCA9IDYsIAogICAgICAgICAgICAgICAgICBzdWJzYW1wbGUgPSAwLjYsICBudW1fY2xhc3MgPSBsZW5ndGgodGFibGUoSWRlbnRzKGRzMikpKSwKICAgICAgICAgICAgICAgICAgb2JqZWN0aXZlID0gIm11bHRpOnNvZnRwcm9iIiwgZXZhbF9tZXRyaWMgPSAnbWxvZ2xvc3MnKQoKYnN0X21vZGVsIDwtIHhnYi50cmFpbih4Z2JfcGFyYW0sIGRzMl90cmFpbiwgbnJvdW5kcyA9IDEwMCwgd2F0Y2hsaXN0LCB2ZXJib3NlID0gMCkKCmV2YWxfbG9zcyA8LSBic3RfbW9kZWxbWyJldmFsdWF0aW9uX2xvZyJdXVtbImV2YWxfbWxvZ2xvc3MiXV0KcGxvdF9seShkYXRhLmZyYW1lKGV2YWxfbG9zcyksIHggPSBjKDE6MTAwKSwgeSA9IGV2YWxfbG9zcykgJT4lIAogIGFkZF90cmFjZSh0eXBlID0gInNjYXR0ZXIiLCBtb2RlID0gIm1hcmtlcnMrbGluZXMiLCAKICAgICAgICAgICAgbWFya2VyID0gbGlzdChjb2xvciA9ICJibGFjayIsIGxpbmUgPSBsaXN0KGNvbG9yID0gIiMxRTkwRkZDNyIsIHdpZHRoID0gMSkpLAogICAgICAgICAgICBsaW5lID0gbGlzdChjb2xvciA9ICIjMUU5MEZGODAiLCB3aWR0aCA9IDIpKSAlPiUgCiAgbGF5b3V0KHhheGlzID0gbGlzdCh0aXRsZSA9ICJlcG9jaCIpLHlheGlzID0gbGlzdCh0aXRsZSA9ICJldmFsX21sb2dsb3NzIikpCmBgYAoKYGBge3IgZmlnLmhlaWdodD02LGZpZy53aWR0aD02fQppbXBvcnRhbmNlIDwtIHhnYi5pbXBvcnRhbmNlKGNvbG5hbWVzKGRzMl90cmFpbiksIG1vZGVsID0gYnN0X21vZGVsKQpoZWFkKGltcG9ydGFuY2UpCnhnYi5nZ3Bsb3QuaW1wb3J0YW5jZShoZWFkKGltcG9ydGFuY2UsMjApLG5fY2x1c3RlcnMgPSAxKSArIHRoZW1lX2J3KCkrdGhlbWUoCiAgICBheGlzLnRpdGxlLnggPSBlbGVtZW50X3RleHQoc2l6ZSA9IDE1KSwgYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoc2l6ZSA9IDgsIGNvbG91ciA9ICJibGFjayIpLAogICAgYXhpcy50aXRsZS55ID0gZWxlbWVudF90ZXh0KHNpemUgPSAxNSksIGF4aXMudGV4dC55ID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMiwgY29sb3VyID0gImJsYWNrIiksCiAgICBsZWdlbmQudGV4dCA9IGVsZW1lbnRfdGV4dChzaXplID0gMjApLCBsZWdlbmQudGl0bGUgPSBlbGVtZW50X2JsYW5rKCksIHBhbmVsLmdyaWQgPSBlbGVtZW50X2JsYW5rKCkpCmBgYAoKCiMjIGRzMiAtPiBkczEKYGBge3J9CklkZW50cyhkczEpIDwtIGRzMSRzZXVyYXRfY2x1c3RlcnMKdGVtcCA8LSBnZXRfZGF0YV90YWJsZShkczEsIGhpZ2h2YXIgPSBGLCB0eXBlID0gImRhdGEiKQpkczFfZGF0YSA8LSBtYXRyaXgoZGF0YT0wLCBucm93ID0gbGVuZ3RoKHJvd25hbWVzKGRzMl9kYXRhKSksIG5jb2wgPSBsZW5ndGgoY29sbmFtZXModGVtcCkpLCAKICAgICAgICAgICAgICAgICAgIGJ5cm93ID0gRkFMU0UsIGRpbW5hbWVzID0gbGlzdChyb3duYW1lcyhkczJfZGF0YSksY29sbmFtZXModGVtcCkpKQpmb3IoaSBpbiBpbnRlcnNlY3Qocm93bmFtZXMoZHMyX2RhdGEpLCByb3duYW1lcyh0ZW1wKSkpewogIGRzMV9kYXRhW2ksXSA8LSB0ZW1wW2ksXQp9CnJtKHRlbXApCmRzMV9sYWJlbCA8LSBhcy5udW1lcmljKGFzLmNoYXJhY3RlcihJZGVudHMoZHMxKSkpCmNvbG5hbWVzKGRzMV9kYXRhKSA8LSBOVUxMCmRzMV90ZXN0X2RhdGEgPC0gbGlzdChkYXRhID0gdChhcyhkczFfZGF0YSwiZGdDTWF0cml4IikpLCBsYWJlbCA9IGRzMV9sYWJlbCkKZHMxX3Rlc3QgPC0geGdiLkRNYXRyaXgoZGF0YSA9IGRzMV90ZXN0X2RhdGEkZGF0YSxsYWJlbCA9IGRzMV90ZXN0X2RhdGEkbGFiZWwpCgoj6aKE5rWL57uT5p6cCgpwcmVkaWN0X2RzMV90ZXN0IDwtIHByZWRpY3QoYnN0X21vZGVsLCBuZXdkYXRhID0gZHMxX3Rlc3QpCgpwcmVkaWN0X3Byb3BfZHMxIDwtIG1hdHJpeChkYXRhPXByZWRpY3RfZHMxX3Rlc3QsIG5yb3cgPSBsZW5ndGgobGV2ZWxzKElkZW50cyhkczIpKSksIAogICAgICAgICAgICAgICAgICAgICAgICAgICBuY29sID0gbmNvbChkczEpLCBieXJvdyA9IEZBTFNFLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgZGltbmFtZXMgPSBsaXN0KGxldmVscyhJZGVudHMoZHMyKSksY29sbmFtZXMoZHMxKSkpCgojIyDlvpfliLDliIbnvqTnu5PmnpwKZHMxX3JlcyA8LSBhcHBseShwcmVkaWN0X3Byb3BfZHMxLDIsZnVuYyxyb3duYW1lcyhwcmVkaWN0X3Byb3BfZHMxKSkKSWRlbnRzKGRzMSkgPC0gZmFjdG9yKGRzMV9yZXMsbGV2ZWxzID0gYygwOjQpKQp1bWFwcGxvdChkczEpCmRzMSRzdXBjbHVzdGVyaW5nIDwtIElkZW50cyhkczEpICPkv53lrZjnm5HnnaPogZrnsbvnu5PmnpwKYGBgCgojIyDmlbDlgLzljJblnLDmipXlsITlm551bWFwCmBgYHtyfQplbWJlZGRpbmcgPC0gRmV0Y2hEYXRhKG9iamVjdCA9IGRzMSwgdmFycyA9IGMoIlVNQVBfMSIsICJVTUFQXzIiKSkKZW1iZWRkaW5nIDwtIGNiaW5kKGVtYmVkZGluZywgdChwcmVkaWN0X3Byb3BfZHMxKSkKCmdnb2JqIDwtIGdncGxvdCgpICsKICBnZW9tX3BvaW50KGRhdGEgPSBlbWJlZGRpbmdbZW1iZWRkaW5nJGAwYD4wLjEsXSwgCiAgICAgICAgICAgICBhZXMoeCA9IFVNQVBfMSwgeSA9IFVNQVBfMiwgY29sb3IgPSBgMGApLCBzaGFwZT0xNiwgc2l6ZSA9IDMsIGFscGhhPTAuNSkgKyAKICBzY2FsZV9jb2xvcl9ncmFkaWVudCgnMCcsIGxvdyA9ICIjRkZGRkZGMDAiLCBoaWdoID0gIiM2ZGMwYTYiKSArCiAgbmV3X3NjYWxlKCJjb2xvciIpICsKICAgIGdlb21fcG9pbnQoZGF0YSA9IGVtYmVkZGluZ1tlbWJlZGRpbmckYDFgPjAuMSxdLCAKICAgICAgICAgICAgIGFlcyh4ID0gVU1BUF8xLCB5ID0gVU1BUF8yLCBjb2xvciA9IGAxYCksc2hhcGU9MTYsIHNpemUgPSAzLCBhbHBoYT0wLjUpICsgCiAgc2NhbGVfY29sb3JfZ3JhZGllbnQoJzEnLCBsb3cgPSAiI0ZGRkZGRjAwIiwgaGlnaCA9ICIjZTJiMzk4IikgKwogICBuZXdfc2NhbGUoImNvbG9yIikgKwogICAgZ2VvbV9wb2ludChkYXRhID0gZW1iZWRkaW5nW2VtYmVkZGluZyRgMmA+MC4xLF0sIAogICAgICAgICAgICAgYWVzKHggPSBVTUFQXzEsIHkgPSBVTUFQXzIsIGNvbG9yID0gYDJgKSxzaGFwZT0xNiwgc2l6ZSA9IDMsIGFscGhhPTAuNSkgKyAKICBzY2FsZV9jb2xvcl9ncmFkaWVudCgnMicsIGxvdyA9ICIjRkZGRkZGMDAiLCBoaWdoID0gIiNlMmEyY2EiKSArCiAgbmV3X3NjYWxlKCJjb2xvciIpICsKICAgIGdlb21fcG9pbnQoZGF0YSA9IGVtYmVkZGluZ1tlbWJlZGRpbmckYDNgPjAuMSxdLCAKICAgICAgICAgICAgIGFlcyh4ID0gVU1BUF8xLCB5ID0gVU1BUF8yLCBjb2xvciA9IGAzYCksc2hhcGU9MTYsIHNpemUgPSAzLCBhbHBoYT0wLjUpICsgCiAgc2NhbGVfY29sb3JfZ3JhZGllbnQoJzMnLCBsb3cgPSAiI0ZGRkZGRjAwIiwgaGlnaCA9ICIjZDFlYmE4IikgKwogICBuZXdfc2NhbGUoImNvbG9yIikgKwogICAgICBnZW9tX3BvaW50KGRhdGEgPSBlbWJlZGRpbmdbZW1iZWRkaW5nJGA0YD4wLjEsXSwgCiAgICAgICAgICAgICBhZXMoeCA9IFVNQVBfMSwgeSA9IFVNQVBfMiwgY29sb3IgPSBgNGApLHNoYXBlPTE2LCBzaXplID0gMywgYWxwaGE9MC41KSArIAogIHNjYWxlX2NvbG9yX2dyYWRpZW50KCc0JywgbG93ID0gIiNGRkZGRkYwMCIsIGhpZ2ggPSAiI2IxZDZmYiIpICsKICAgIG5ld19zY2FsZSgiY29sb3IiKSArCiAgICAgICAgeGxhYigiVU1BUCAxIikgKyB5bGFiKCJVTUFQIDIiKSAgKwogICAgICAgIHRoZW1lKGF4aXMubGluZSA9IGVsZW1lbnRfbGluZShhcnJvdyA9IGFycm93KGxlbmd0aCA9IHVuaXQoMC4yLCAiY20iKSkpKSArCiAgICAgICAgc2NhbGVfeV9jb250aW51b3VzKGJyZWFrcyA9IE5VTEwpICsKICAgICAgICBzY2FsZV94X2NvbnRpbnVvdXMoYnJlYWtzID0gTlVMTCkgKyAKICB0aGVtZShwYW5lbC5iYWNrZ3JvdW5kID0gZWxlbWVudF9ibGFuaygpLCBwYW5lbC5ncmlkID0gZWxlbWVudF9ibGFuaygpLCBsZWdlbmQucG9zaXRpb24gPSAiYm90dG9tIikKZ2dzYXZlKCJwcmVfZHMxX3VtYXAuc3ZnIixkZXZpY2UgPSBzdmcscGxvdCA9IGdnb2JqLGhlaWdodCA9IDEwLHdpZHRoID0gMTApCmBgYAoKI2RzMiAtPiBkczAKYGBge3J9CklkZW50cyhkczApIDwtIGRzMCRzZXVyYXRfY2x1c3RlcnMKdGVtcCA8LSBnZXRfZGF0YV90YWJsZShkczAsIGhpZ2h2YXIgPSBGLCB0eXBlID0gImRhdGEiKQpkczBfZGF0YSA8LSBtYXRyaXgoZGF0YT0wLCBucm93ID0gbGVuZ3RoKHJvd25hbWVzKGRzMl9kYXRhKSksIG5jb2wgPSBsZW5ndGgoY29sbmFtZXModGVtcCkpLCAKICAgICAgICAgICAgICAgICAgIGJ5cm93ID0gRkFMU0UsIGRpbW5hbWVzID0gbGlzdChyb3duYW1lcyhkczJfZGF0YSksY29sbmFtZXModGVtcCkpKQpmb3IoaSBpbiBpbnRlcnNlY3Qocm93bmFtZXMoZHMyX2RhdGEpLCByb3duYW1lcyh0ZW1wKSkpewogIGRzMF9kYXRhW2ksXSA8LSB0ZW1wW2ksXQp9CnJtKHRlbXApCmRzMF9sYWJlbCA8LSBhcy5udW1lcmljKGFzLmNoYXJhY3RlcihJZGVudHMoZHMwKSkpCmNvbG5hbWVzKGRzMF9kYXRhKSA8LSBOVUxMCmRzMF90ZXN0X2RhdGEgPC0gbGlzdChkYXRhID0gdChhcyhkczBfZGF0YSwiZGdDTWF0cml4IikpLCBsYWJlbCA9IGRzMF9sYWJlbCkKZHMwX3Rlc3QgPC0geGdiLkRNYXRyaXgoZGF0YSA9IGRzMF90ZXN0X2RhdGEkZGF0YSxsYWJlbCA9IGRzMF90ZXN0X2RhdGEkbGFiZWwpCgoj6aKE5rWL57uT5p6cCgpwcmVkaWN0X2RzMF90ZXN0IDwtIHByZWRpY3QoYnN0X21vZGVsLCBuZXdkYXRhID0gZHMwX3Rlc3QpCgpwcmVkaWN0X3Byb3BfZHMwIDwtIG1hdHJpeChkYXRhPXByZWRpY3RfZHMwX3Rlc3QsIG5yb3cgPSBsZW5ndGgobGV2ZWxzKElkZW50cyhkczIpKSksIAogICAgICAgICAgICAgICAgICAgICAgICAgICBuY29sID0gbmNvbChkczApLCBieXJvdyA9IEZBTFNFLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgZGltbmFtZXMgPSBsaXN0KGxldmVscyhJZGVudHMoZHMyKSksY29sbmFtZXMoZHMwKSkpCgojIyDlvpfliLDliIbnvqTnu5PmnpwKZHMwX3JlcyA8LSBhcHBseShwcmVkaWN0X3Byb3BfZHMwLDIsZnVuYyxyb3duYW1lcyhwcmVkaWN0X3Byb3BfZHMwKSkKSWRlbnRzKGRzMCkgPC0gZmFjdG9yKGRzMF9yZXMsbGV2ZWxzID0gYygwOjQpKQp1bWFwcGxvdChkczApCmRzMCRzdXBjbHVzdGVyaW5nIDwtIElkZW50cyhkczApICPkv53lrZjnm5HnnaPogZrnsbvnu5PmnpwKYGBgCgpgYGB7cn0KZW1iZWRkaW5nIDwtIEZldGNoRGF0YShvYmplY3QgPSBkczAsIHZhcnMgPSBjKCJVTUFQXzEiLCAiVU1BUF8yIikpCmVtYmVkZGluZyA8LSBjYmluZChlbWJlZGRpbmcsIHQocHJlZGljdF9wcm9wX2RzMCkpCgpnZ29iaiA8LSBnZ3Bsb3QoKSArCiAgZ2VvbV9wb2ludChkYXRhID0gZW1iZWRkaW5nW2VtYmVkZGluZyRgMGA+MC4xLF0sIAogICAgICAgICAgICAgYWVzKHggPSBVTUFQXzEsIHkgPSBVTUFQXzIsIGNvbG9yID0gYDBgKSwgc2hhcGU9MTYsIHNpemUgPSAzLCBhbHBoYT0wLjUpICsgCiAgc2NhbGVfY29sb3JfZ3JhZGllbnQoJzAnLCBsb3cgPSAiI0ZGRkZGRjAwIiwgaGlnaCA9ICIjNmRjMGE2IikgKwogIG5ld19zY2FsZSgiY29sb3IiKSArCiAgICBnZW9tX3BvaW50KGRhdGEgPSBlbWJlZGRpbmdbZW1iZWRkaW5nJGAxYD4wLjEsXSwgCiAgICAgICAgICAgICBhZXMoeCA9IFVNQVBfMSwgeSA9IFVNQVBfMiwgY29sb3IgPSBgMWApLHNoYXBlPTE2LCBzaXplID0gMywgYWxwaGE9MC41KSArIAogIHNjYWxlX2NvbG9yX2dyYWRpZW50KCcxJywgbG93ID0gIiNGRkZGRkYwMCIsIGhpZ2ggPSAiI2UyYjM5OCIpICsKICAgbmV3X3NjYWxlKCJjb2xvciIpICsKICAgIGdlb21fcG9pbnQoZGF0YSA9IGVtYmVkZGluZ1tlbWJlZGRpbmckYDJgPjAuMSxdLCAKICAgICAgICAgICAgIGFlcyh4ID0gVU1BUF8xLCB5ID0gVU1BUF8yLCBjb2xvciA9IGAyYCksc2hhcGU9MTYsIHNpemUgPSAzLCBhbHBoYT0wLjUpICsgCiAgc2NhbGVfY29sb3JfZ3JhZGllbnQoJzInLCBsb3cgPSAiI0ZGRkZGRjAwIiwgaGlnaCA9ICIjZTJhMmNhIikgKwogIG5ld19zY2FsZSgiY29sb3IiKSArCiAgICBnZW9tX3BvaW50KGRhdGEgPSBlbWJlZGRpbmdbZW1iZWRkaW5nJGAzYD4wLjEsXSwgCiAgICAgICAgICAgICBhZXMoeCA9IFVNQVBfMSwgeSA9IFVNQVBfMiwgY29sb3IgPSBgM2ApLHNoYXBlPTE2LCBzaXplID0gMywgYWxwaGE9MC41KSArIAogIHNjYWxlX2NvbG9yX2dyYWRpZW50KCczJywgbG93ID0gIiNGRkZGRkYwMCIsIGhpZ2ggPSAiI2QxZWJhOCIpICsKICAgbmV3X3NjYWxlKCJjb2xvciIpICsKICAgICAgZ2VvbV9wb2ludChkYXRhID0gZW1iZWRkaW5nW2VtYmVkZGluZyRgNGA+MC4xLF0sIAogICAgICAgICAgICAgYWVzKHggPSBVTUFQXzEsIHkgPSBVTUFQXzIsIGNvbG9yID0gYDRgKSxzaGFwZT0xNiwgc2l6ZSA9IDMsIGFscGhhPTAuNSkgKyAKICBzY2FsZV9jb2xvcl9ncmFkaWVudCgnNCcsIGxvdyA9ICIjRkZGRkZGMDAiLCBoaWdoID0gIiNiMWQ2ZmIiKSArCiAgICBuZXdfc2NhbGUoImNvbG9yIikgKwogICAgICAgIHhsYWIoIlVNQVAgMSIpICsgeWxhYigiVU1BUCAyIikgICsKICAgICAgICB0aGVtZShheGlzLmxpbmUgPSBlbGVtZW50X2xpbmUoYXJyb3cgPSBhcnJvdyhsZW5ndGggPSB1bml0KDAuMiwgImNtIikpKSkgKwogICAgICAgIHNjYWxlX3lfY29udGludW91cyhicmVha3MgPSBOVUxMKSArCiAgICAgICAgc2NhbGVfeF9jb250aW51b3VzKGJyZWFrcyA9IE5VTEwpICsgCiAgdGhlbWUocGFuZWwuYmFja2dyb3VuZCA9IGVsZW1lbnRfYmxhbmsoKSwgcGFuZWwuZ3JpZCA9IGVsZW1lbnRfYmxhbmsoKSwgbGVnZW5kLnBvc2l0aW9uID0gImJvdHRvbSIpCmdnc2F2ZSgicHJlX2RzMF91bWFwLnN2ZyIsZGV2aWNlID0gc3ZnLHBsb3QgPSBnZ29iaixoZWlnaHQgPSAxMCx3aWR0aCA9IDEwKQpgYGAKCgojIFBBIC0+IEFDCmBgYHtyfQpJZGVudHMoZHMyX1BBKSA8LSBkczJfUEEkc2V1cmF0X2NsdXN0ZXJzCnNlbGVjdGVkX2ZlYXR1cmVzIDwtIHJlYWQuY3N2KCIuL2RhdGF0YWJsZS9zZWxlY3RlZF9mZWF0dXJlcy5jc3YiLCBzdHJpbmdzQXNGYWN0b3JzID0gRikKc2VsZWN0ZWRfZmVhdHVyZXMgPC0gc2VsZWN0ZWRfZmVhdHVyZXMkeApQQV9kYXRhIDwtIGdldF9kYXRhX3RhYmxlKGRzMl9QQSwgaGlnaHZhciA9IEYsIHR5cGUgPSAiZGF0YSIpClBBX2RhdGEgPC0gUEFfZGF0YVtzZWxlY3RlZF9mZWF0dXJlcyxdClBBX2xhYmVsIDwtIGFzLm51bWVyaWMoYXMuY2hhcmFjdGVyKElkZW50cyhkczJfUEEpKSkKY29sbmFtZXMoUEFfZGF0YSkgPC0gTlVMTAoKUEFfdHJhaW5fZGF0YSA8LSBsaXN0KGRhdGEgPSB0KGFzKFBBX2RhdGEsImRnQ01hdHJpeCIpKSwgbGFiZWwgPSBQQV9sYWJlbCkKUEFfdHJhaW4gPC0geGdiLkRNYXRyaXgoZGF0YSA9IFBBX3RyYWluX2RhdGEkZGF0YSxsYWJlbCA9IFBBX3RyYWluX2RhdGEkbGFiZWwpCnhnYl9wYXJhbSA8LSBsaXN0KGV0YSA9IDAuMiwgbWF4X2RlcHRoID0gNiwgCiAgICAgICAgICAgICAgICAgIHN1YnNhbXBsZSA9IDAuNiwgIG51bV9jbGFzcyA9IGxlbmd0aCh0YWJsZShJZGVudHMoZHMyX1BBKSkpLAogICAgICAgICAgICAgICAgICBvYmplY3RpdmUgPSAibXVsdGk6c29mdHByb2IiLCBldmFsX21ldHJpYyA9ICdtbG9nbG9zcycpCgpic3RfbW9kZWwgPC0geGdiLnRyYWluKHhnYl9wYXJhbSwgUEFfdHJhaW4sIG5yb3VuZHMgPSAxMDAsIHZlcmJvc2UgPSAwKQpgYGAKCmBgYHtyfQpJZGVudHMoZHMyX0FDKSA8LSBkczJfQUMkc2V1cmF0X2NsdXN0ZXJzCkFDX2RhdGEgPC0gZ2V0X2RhdGFfdGFibGUoZHMyX0FDLCBoaWdodmFyID0gRiwgdHlwZSA9ICJkYXRhIikKQUNfZGF0YSA8LSBBQ19kYXRhW3NlbGVjdGVkX2ZlYXR1cmVzLF0KQUNfbGFiZWwgPC0gYXMubnVtZXJpYyhhcy5jaGFyYWN0ZXIoSWRlbnRzKGRzMl9BQykpKQpjb2xuYW1lcyhBQ19kYXRhKSA8LSBOVUxMCkFDX3Rlc3RfZGF0YSA8LSBsaXN0KGRhdGEgPSB0KGFzKEFDX2RhdGEsImRnQ01hdHJpeCIpKSwgbGFiZWwgPSBBQ19sYWJlbCkKQUNfdGVzdCA8LSB4Z2IuRE1hdHJpeChkYXRhID0gQUNfdGVzdF9kYXRhJGRhdGEsbGFiZWwgPSBBQ190ZXN0X2RhdGEkbGFiZWwpCgoj6aKE5rWL57uT5p6cCnByZWRpY3RfcHJvcF9BQyA8LXByZWRpY3QoYnN0X21vZGVsLCBuZXdkYXRhID0gQUNfdGVzdCkgJT4lCiBtYXRyaXgobnJvdyA9IGxlbmd0aChsZXZlbHMoSWRlbnRzKGRzMl9QQSkpKSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgIG5jb2wgPSBuY29sKGRzMl9BQyksIGJ5cm93ID0gRkFMU0UsIAogICAgICAgICAgICAgICAgICAgICAgICAgICBkaW1uYW1lcyA9IGxpc3QobGV2ZWxzKElkZW50cyhkczJfUEEpKSxjb2xuYW1lcyhkczJfQUMpKSkKQUNfcmVzIDwtIGFwcGx5KHByZWRpY3RfcHJvcF9BQywyLGZ1bmMscm93bmFtZXMocHJlZGljdF9wcm9wX0FDKSkKCmNvbmZ1c2VfbWF0cml4MSA8LSB0YWJsZShBQ190ZXN0X2RhdGEkbGFiZWwsIEFDX3JlcywgZG5uPWMoInRydWUiLCJwcmUiKSkKc2Fua2V5X3Bsb3QoY29uZnVzZV9tYXRyaXgxLHNlc3Npb24gPSAiUEF0b0FDIikKCklkZW50cyhkczJfQUMpIDwtIGZhY3RvcihBQ19yZXMsbGV2ZWxzID0gYygwOjIpKQp1bWFwcGxvdChkczJfQUMpCmBgYAoKYGBge3J9CmVtYmVkZGluZyA8LSBGZXRjaERhdGEob2JqZWN0ID0gZHMyX0FDLCB2YXJzID0gYygiVU1BUF8xIiwgIlVNQVBfMiIpKQplbWJlZGRpbmcgPC0gY2JpbmQoZW1iZWRkaW5nLCB0KHByZWRpY3RfcHJvcF9BQykpCgpnZ29iaiA8LSBnZ3Bsb3QoKSArCiAgZ2VvbV9wb2ludChkYXRhID0gZW1iZWRkaW5nW2VtYmVkZGluZyRgMGA+MC4xLF0sIAogICAgICAgICAgICAgYWVzKHggPSBVTUFQXzEsIHkgPSBVTUFQXzIsIGNvbG9yID0gYDBgKSwgc2hhcGU9MTYsIHNpemUgPSAyLCBhbHBoYT0wLjUpICsgCiAgc2NhbGVfY29sb3JfZ3JhZGllbnQoJzAnLCBsb3cgPSAiI0ZGRkZGRjAwIiwgaGlnaCA9ICIjNmRjMGE2IikgKwogIG5ld19zY2FsZSgiY29sb3IiKSArCiAgICBnZW9tX3BvaW50KGRhdGEgPSBlbWJlZGRpbmdbZW1iZWRkaW5nJGAxYD4wLjEsXSwgCiAgICAgICAgICAgICBhZXMoeCA9IFVNQVBfMSwgeSA9IFVNQVBfMiwgY29sb3IgPSBgMWApLHNoYXBlPTE2LCBzaXplID0gMiwgYWxwaGE9MC41KSArIAogIHNjYWxlX2NvbG9yX2dyYWRpZW50KCcxJywgbG93ID0gIiNGRkZGRkYwMCIsIGhpZ2ggPSAiI2UyYjM5OCIpICsKICAgbmV3X3NjYWxlKCJjb2xvciIpICsKICAgIGdlb21fcG9pbnQoZGF0YSA9IGVtYmVkZGluZ1tlbWJlZGRpbmckYDJgPjAuMSxdLCAKICAgICAgICAgICAgIGFlcyh4ID0gVU1BUF8xLCB5ID0gVU1BUF8yLCBjb2xvciA9IGAyYCksc2hhcGU9MTYsIHNpemUgPSAyLCBhbHBoYT0wLjUpICsgCiAgc2NhbGVfY29sb3JfZ3JhZGllbnQoJzInLCBsb3cgPSAiI0ZGRkZGRjAwIiwgaGlnaCA9ICIjZTJhMmNhIikgKwogICAgICAgIHhsYWIoIlVNQVAgMSIpICsgeWxhYigiVU1BUCAyIikgICsKICAgICAgICB0aGVtZShheGlzLmxpbmUgPSBlbGVtZW50X2xpbmUoYXJyb3cgPSBhcnJvdyhsZW5ndGggPSB1bml0KDAuMiwgImNtIikpKSkgKwogICAgICAgIHNjYWxlX3lfY29udGludW91cyhicmVha3MgPSBOVUxMKSArCiAgICAgICAgc2NhbGVfeF9jb250aW51b3VzKGJyZWFrcyA9IE5VTEwpICsgCiAgdGhlbWUocGFuZWwuYmFja2dyb3VuZCA9IGVsZW1lbnRfYmxhbmsoKSwgcGFuZWwuZ3JpZCA9IGVsZW1lbnRfYmxhbmsoKSwgbGVnZW5kLnBvc2l0aW9uID0gImJvdHRvbSIpCmdnc2F2ZSgiZHMyX1BBdG9BQ191bWFwLnN2ZyIsZGV2aWNlID0gc3ZnLHBsb3QgPSBnZ29iaixoZWlnaHQgPSA4LHdpZHRoID0gOCkKYGBgCgoKCiMjIEFDIHRvIFBBCmBgYHtyfQpJZGVudHMoZHMyX0FDKSA8LSBkczJfQUMkc2V1cmF0X2NsdXN0ZXJzCnNlbGVjdGVkX2ZlYXR1cmVzIDwtIHJlYWQuY3N2KCIuL2RhdGF0YWJsZS9zZWxlY3RlZF9mZWF0dXJlcy5jc3YiLCBzdHJpbmdzQXNGYWN0b3JzID0gRikKc2VsZWN0ZWRfZmVhdHVyZXMgPC0gc2VsZWN0ZWRfZmVhdHVyZXMkeApBQ19kYXRhIDwtIGdldF9kYXRhX3RhYmxlKGRzMl9BQywgaGlnaHZhciA9IEYsIHR5cGUgPSAiZGF0YSIpCkFDX2RhdGEgPC0gQUNfZGF0YVtzZWxlY3RlZF9mZWF0dXJlcyxdCkFDX2xhYmVsIDwtIGFzLm51bWVyaWMoYXMuY2hhcmFjdGVyKElkZW50cyhkczJfQUMpKSkKY29sbmFtZXMoQUNfZGF0YSkgPC0gTlVMTAoKQUNfdHJhaW5fZGF0YSA8LSBsaXN0KGRhdGEgPSB0KGFzKEFDX2RhdGEsImRnQ01hdHJpeCIpKSwgbGFiZWwgPSBBQ19sYWJlbCkKQUNfdHJhaW4gPC0geGdiLkRNYXRyaXgoZGF0YSA9IEFDX3RyYWluX2RhdGEkZGF0YSxsYWJlbCA9IEFDX3RyYWluX2RhdGEkbGFiZWwpCnhnYl9BQ3JhbSA8LSBsaXN0KGV0YSA9IDAuMiwgbWF4X2RlcHRoID0gNiwgCiAgICAgICAgICAgICAgICAgIHN1YnNhbXBsZSA9IDAuNiwgIG51bV9jbGFzcyA9IGxlbmd0aCh0YWJsZShJZGVudHMoZHMyX0FDKSkpLAogICAgICAgICAgICAgICAgICBvYmplY3RpdmUgPSAibXVsdGk6c29mdHByb2IiLCBldmFsX21ldHJpYyA9ICdtbG9nbG9zcycpCgpic3RfbW9kZWwgPC0geGdiLnRyYWluKHhnYl9BQ3JhbSwgQUNfdHJhaW4sIG5yb3VuZHMgPSAxMDAsIHZlcmJvc2UgPSAwKQpgYGAKCmBgYHtyfQpJZGVudHMoZHMyX1BBKSA8LSBmYWN0b3IoZHMyX1BBJHNldXJhdF9jbHVzdGVycyxsZXZlbHMgPSBjKDAsMSwyKSkKClBBX2RhdGEgPC0gZ2V0X2RhdGFfdGFibGUoZHMyX1BBLCBoaWdodmFyID0gRiwgdHlwZSA9ICJkYXRhIikKUEFfZGF0YSA8LSBQQV9kYXRhW3NlbGVjdGVkX2ZlYXR1cmVzLF0KUEFfbGFiZWwgPC0gYXMubnVtZXJpYyhhcy5jaGFyYWN0ZXIoSWRlbnRzKGRzMl9QQSkpKQpjb2xuYW1lcyhQQV9kYXRhKSA8LSBOVUxMClBBX3Rlc3RfZGF0YSA8LSBsaXN0KGRhdGEgPSB0KGFzKFBBX2RhdGEsImRnQ01hdHJpeCIpKSwgbGFiZWwgPSBQQV9sYWJlbCkKUEFfdGVzdCA8LSB4Z2IuRE1hdHJpeChkYXRhID0gUEFfdGVzdF9kYXRhJGRhdGEsbGFiZWwgPSBQQV90ZXN0X2RhdGEkbGFiZWwpCgoj6aKE5rWL57uT5p6cCnByZWRpY3RfcHJvcF9QQSA8LXByZWRpY3QoYnN0X21vZGVsLCBuZXdkYXRhID0gUEFfdGVzdCkgJT4lCiBtYXRyaXgobnJvdyA9IGxlbmd0aChsZXZlbHMoSWRlbnRzKGRzMl9BQykpKSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgIG5jb2wgPSBuY29sKGRzMl9QQSksIGJ5cm93ID0gRkFMU0UsIAogICAgICAgICAgICAgICAgICAgICAgICAgICBkaW1uYW1lcyA9IGxpc3QobGV2ZWxzKElkZW50cyhkczJfQUMpKSxjb2xuYW1lcyhkczJfUEEpKSkKUEFfcmVzIDwtIGFwcGx5KHByZWRpY3RfcHJvcF9QQSwyLGZ1bmMscm93bmFtZXMocHJlZGljdF9wcm9wX1BBKSkKCmNvbmZ1c2VfbWF0cml4MSA8LSB0YWJsZShQQV90ZXN0X2RhdGEkbGFiZWwsIFBBX3JlcywgZG5uPWMoInRydWUiLCJwcmUiKSkKc2Fua2V5X3Bsb3QoY29uZnVzZV9tYXRyaXgxLHNlc3Npb24gPSAiQUN0b1BBIikKCklkZW50cyhkczJfUEEpIDwtIGZhY3RvcihQQV9yZXMpCnVtYXBwbG90KGRzMl9QQSkKYGBgCgpgYGB7cn0KZW1iZWRkaW5nIDwtIEZldGNoRGF0YShvYmplY3QgPSBkczJfUEEsIHZhcnMgPSBjKCJVTUFQXzEiLCAiVU1BUF8yIikpCmVtYmVkZGluZyA8LSBjYmluZChlbWJlZGRpbmcsIHQocHJlZGljdF9wcm9wX1BBKSkKCmdnb2JqIDwtIGdncGxvdCgpICsKICBnZW9tX3BvaW50KGRhdGEgPSBlbWJlZGRpbmdbZW1iZWRkaW5nJGAwYD4wLjEsXSwgCiAgICAgICAgICAgICBhZXMoeCA9IFVNQVBfMSwgeSA9IFVNQVBfMiwgY29sb3IgPSBgMGApLCBzaGFwZT0xNiwgc2l6ZSA9IDIsIGFscGhhPTAuNSkgKyAKICBzY2FsZV9jb2xvcl9ncmFkaWVudCgnMCcsIGxvdyA9ICIjRkZGRkZGMDAiLCBoaWdoID0gIiM2ZGMwYTYiKSArCiAgbmV3X3NjYWxlKCJjb2xvciIpICsKICAgIGdlb21fcG9pbnQoZGF0YSA9IGVtYmVkZGluZ1tlbWJlZGRpbmckYDFgPjAuMSxdLCAKICAgICAgICAgICAgIGFlcyh4ID0gVU1BUF8xLCB5ID0gVU1BUF8yLCBjb2xvciA9IGAxYCksc2hhcGU9MTYsIHNpemUgPSAyLCBhbHBoYT0wLjUpICsgCiAgc2NhbGVfY29sb3JfZ3JhZGllbnQoJzEnLCBsb3cgPSAiI0ZGRkZGRjAwIiwgaGlnaCA9ICIjZTJiMzk4IikgKwogICBuZXdfc2NhbGUoImNvbG9yIikgKwogICAgZ2VvbV9wb2ludChkYXRhID0gZW1iZWRkaW5nW2VtYmVkZGluZyRgMmA+MC4xLF0sIAogICAgICAgICAgICAgYWVzKHggPSBVTUFQXzEsIHkgPSBVTUFQXzIsIGNvbG9yID0gYDJgKSxzaGFwZT0xNiwgc2l6ZSA9IDIsIGFscGhhPTAuNSkgKyAKICBzY2FsZV9jb2xvcl9ncmFkaWVudCgnMicsIGxvdyA9ICIjRkZGRkZGMDAiLCBoaWdoID0gIiNlMmEyY2EiKSArCiAgICAgbmV3X3NjYWxlKCJjb2xvciIpICsKICAgIGdlb21fcG9pbnQoZGF0YSA9IGVtYmVkZGluZ1tlbWJlZGRpbmckYDNgPjAuMSxdLCAKICAgICAgICAgICAgIGFlcyh4ID0gVU1BUF8xLCB5ID0gVU1BUF8yLCBjb2xvciA9IGAzYCksc2hhcGU9MTYsIHNpemUgPSAyLCBhbHBoYT0wLjUpICsgCiAgc2NhbGVfY29sb3JfZ3JhZGllbnQoJzMnLCBsb3cgPSAiI0ZGRkZGRjAwIiwgaGlnaCA9ICIjZDFlYmE4IikgKwogICAgICAgIHhsYWIoIlVNQVAgMSIpICsgeWxhYigiVU1BUCAyIikgICsKICAgICAgICB0aGVtZShheGlzLmxpbmUgPSBlbGVtZW50X2xpbmUoYXJyb3cgPSBhcnJvdyhsZW5ndGggPSB1bml0KDAuMiwgImNtIikpKSkgKwogICAgICAgIHNjYWxlX3lfY29udGludW91cyhicmVha3MgPSBOVUxMKSArCiAgICAgICAgc2NhbGVfeF9jb250aW51b3VzKGJyZWFrcyA9IE5VTEwpICsgCiAgdGhlbWUocGFuZWwuYmFja2dyb3VuZCA9IGVsZW1lbnRfYmxhbmsoKSwgcGFuZWwuZ3JpZCA9IGVsZW1lbnRfYmxhbmsoKSwgbGVnZW5kLnBvc2l0aW9uID0gImJvdHRvbSIpCmdnc2F2ZSgiZHMyX0FDdG9QQV91bWFwLnN2ZyIsZGV2aWNlID0gc3ZnLHBsb3QgPSBnZ29iaixoZWlnaHQgPSA4LHdpZHRoID0gOCkKYGBgCgoKIyMg5ZyoZHMw5LiK6K6t57uDCmBgYHtyfQpJZGVudHMoZHMwKSA8LSBkczAkc2V1cmF0X2NsdXN0ZXJzCmRzMF9kYXRhIDwtIGdldF9kYXRhX3RhYmxlKGRzMCwgaGlnaHZhciA9IEYsIHR5cGUgPSAiZGF0YSIpCmRzMF9sYWJlbCA8LSBhcy5udW1lcmljKGFzLmNoYXJhY3RlcihJZGVudHMoZHMwKSkpCgppbmRleCA8LSBjKDE6ZGltKGRzMF9kYXRhKVsyXSkgJT4lIHNhbXBsZShjZWlsaW5nKDAuMypkaW0oZHMwX2RhdGEpWzJdKSwgcmVwbGFjZSA9IEYsIHByb2IgPSBOVUxMKQpjb2xuYW1lcyhkczBfZGF0YSkgPC0gTlVMTAoKZHMwX3RyYWluX2RhdGEgPC0gbGlzdChkYXRhID0gdChhcyhkczBfZGF0YVssLWluZGV4XSwiZGdDTWF0cml4IikpLCBsYWJlbCA9IGRzMF9sYWJlbFstaW5kZXhdKQpkczBfdGVzdF9kYXRhIDwtIGxpc3QoZGF0YSA9IHQoYXMoZHMwX2RhdGFbLGluZGV4XSwiZGdDTWF0cml4IikpLCBsYWJlbCA9IGRzMF9sYWJlbFtpbmRleF0pCgpkczBfdHJhaW4gPC0geGdiLkRNYXRyaXgoZGF0YSA9IGRzMF90cmFpbl9kYXRhJGRhdGEsbGFiZWwgPSBkczBfdHJhaW5fZGF0YSRsYWJlbCkKZHMwX3Rlc3QgPC0geGdiLkRNYXRyaXgoZGF0YSA9IGRzMF90ZXN0X2RhdGEkZGF0YSxsYWJlbCA9IGRzMF90ZXN0X2RhdGEkbGFiZWwpCgp3YXRjaGxpc3QgPC0gbGlzdCh0cmFpbiA9IGRzMF90cmFpbiwgZXZhbCA9IGRzMF90ZXN0KQp4Z2JfcGFyYW0gPC0gbGlzdChldGEgPSAwLjIsIG1heF9kZXB0aCA9IDYsIAogICAgICAgICAgICAgICAgICBzdWJzYW1wbGUgPSAwLjYsICBudW1fY2xhc3MgPSBsZW5ndGgodGFibGUoSWRlbnRzKGRzMCkpKSwKICAgICAgICAgICAgICAgICAgb2JqZWN0aXZlID0gIm11bHRpOnNvZnRwcm9iIiwgZXZhbF9tZXRyaWMgPSAnbWxvZ2xvc3MnKQoKYnN0X21vZGVsIDwtIHhnYi50cmFpbih4Z2JfcGFyYW0sIGRzMF90cmFpbiwgbnJvdW5kcyA9IDEwMCwgd2F0Y2hsaXN0LCB2ZXJib3NlID0gMCkKCmV2YWxfbG9zcyA8LSBic3RfbW9kZWxbWyJldmFsdWF0aW9uX2xvZyJdXVtbImV2YWxfbWxvZ2xvc3MiXV0KcGxvdF9seShkYXRhLmZyYW1lKGV2YWxfbG9zcyksIHggPSBjKDE6MTAwKSwgeSA9IGV2YWxfbG9zcykgJT4lIAogIGFkZF90cmFjZSh0eXBlID0gInNjYXR0ZXIiLCBtb2RlID0gIm1hcmtlcnMrbGluZXMiLCAKICAgICAgICAgICAgbWFya2VyID0gbGlzdChjb2xvciA9ICJibGFjayIsIGxpbmUgPSBsaXN0KGNvbG9yID0gIiMxRTkwRkZDNyIsIHdpZHRoID0gMSkpLAogICAgICAgICAgICBsaW5lID0gbGlzdChjb2xvciA9ICIjMUU5MEZGODAiLCB3aWR0aCA9IDIpKSAlPiUgCiAgbGF5b3V0KHhheGlzID0gbGlzdCh0aXRsZSA9ICJlcG9jaCIpLHlheGlzID0gbGlzdCh0aXRsZSA9ICJldmFsX21sb2dsb3NzIikpCmBgYAoKYGBge3IgZmlnLndpZHRoPTYsZmlnLmhlaWdodD02fQppbXBvcnRhbmNlIDwtIHhnYi5pbXBvcnRhbmNlKGNvbG5hbWVzKGRzMF90cmFpbiksIG1vZGVsID0gYnN0X21vZGVsKQpoZWFkKGltcG9ydGFuY2UpCnhnYi5nZ3Bsb3QuaW1wb3J0YW5jZShoZWFkKGltcG9ydGFuY2UsMjApLG5fY2x1c3RlcnMgPSAxKSArIHRoZW1lX2J3KCkrdGhlbWUoCiAgICBheGlzLnRpdGxlLnggPSBlbGVtZW50X3RleHQoc2l6ZSA9IDE1KSwgYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoc2l6ZSA9IDgsIGNvbG91ciA9ICJibGFjayIpLAogICAgYXhpcy50aXRsZS55ID0gZWxlbWVudF90ZXh0KHNpemUgPSAxNSksIGF4aXMudGV4dC55ID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMiwgY29sb3VyID0gImJsYWNrIiksCiAgICBsZWdlbmQudGV4dCA9IGVsZW1lbnRfdGV4dChzaXplID0gMjApLCBsZWdlbmQudGl0bGUgPSBlbGVtZW50X2JsYW5rKCksIHBhbmVsLmdyaWQgPSBlbGVtZW50X2JsYW5rKCkpCndyaXRlLmNzdihpbXBvcnRhbmNlLCAiLi9kYXRhdGFibGUvZHMwX2ZlYXR1cmVzLmNzdiIsIHJvdy5uYW1lcyA9IEYpCm11bHRpX2ZlYXR1cmVwbG90KGhlYWQoaW1wb3J0YW5jZSw5KSRGZWF0dXJlLCBkczAsIGxhYmVscyA9ICIiKSAKYGBgCiMjIGRzMCAtPiBkczIKYGBge3J9CklkZW50cyhkczIpIDwtIGRzMiRzZXVyYXRfY2x1c3RlcnMgCnRlbXAgPC0gZ2V0X2RhdGFfdGFibGUoZHMyLCBoaWdodmFyID0gRiwgdHlwZSA9ICJkYXRhIikKZHMyX2RhdGEgPC0gbWF0cml4KGRhdGE9MCwgbnJvdyA9IGxlbmd0aChyb3duYW1lcyhkczBfZGF0YSkpLCBuY29sID0gbGVuZ3RoKGNvbG5hbWVzKHRlbXApKSwgCiAgICAgICAgICAgICAgICAgICBieXJvdyA9IEZBTFNFLCBkaW1uYW1lcyA9IGxpc3Qocm93bmFtZXMoZHMwX2RhdGEpLGNvbG5hbWVzKHRlbXApKSkKZm9yKGkgaW4gaW50ZXJzZWN0KHJvd25hbWVzKGRzMl9kYXRhKSwgcm93bmFtZXModGVtcCkpKXsKICBkczJfZGF0YVtpLF0gPC0gdGVtcFtpLF0KfQpybSh0ZW1wKQpkczJfbGFiZWwgPC0gYXMubnVtZXJpYyhhcy5jaGFyYWN0ZXIoSWRlbnRzKGRzMikpKQpjb2xuYW1lcyhkczJfZGF0YSkgPC0gTlVMTApkczJfdGVzdF9kYXRhIDwtIGxpc3QoZGF0YSA9IHQoYXMoZHMyX2RhdGEsImRnQ01hdHJpeCIpKSwgbGFiZWwgPSBkczJfbGFiZWwpCmRzMl90ZXN0IDwtIHhnYi5ETWF0cml4KGRhdGEgPSBkczJfdGVzdF9kYXRhJGRhdGEsbGFiZWwgPSBkczJfdGVzdF9kYXRhJGxhYmVsKQoKI+mihOa1i+e7k+aenAoKcHJlZGljdF9kczJfdGVzdCA8LSBwcmVkaWN0KGJzdF9tb2RlbCwgbmV3ZGF0YSA9IGRzMl90ZXN0KQoKcHJlZGljdF9wcm9wX2RzMiA8LSBtYXRyaXgoZGF0YT1wcmVkaWN0X2RzMl90ZXN0LCBucm93ID0gYnN0X21vZGVsW1sicGFyYW1zIl1dW1sibnVtX2NsYXNzIl1dLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgbmNvbCA9IG5jb2woZHMyKSwgYnlyb3cgPSBGQUxTRSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgIGRpbW5hbWVzID0gbGlzdChjKDA6KGJzdF9tb2RlbFtbInBhcmFtcyJdXVtbIm51bV9jbGFzcyJdXS0xKSksY29sbmFtZXMoZHMyKSkpCgojIyDlvpfliLDliIbnvqTnu5PmnpwKZHMyX3JlcyA8LSBhcHBseShwcmVkaWN0X3Byb3BfZHMyLDIsZnVuYyxyb3duYW1lcyhwcmVkaWN0X3Byb3BfZHMyKSkKY29uZnVzZV9tYXRyaXgxIDwtIHRhYmxlKGRzMl90ZXN0X2RhdGEkbGFiZWwsIGRzMl9yZXMsIGRubj1jKCJ0cnVlIiwicHJlIikpCgpzYW5rZXlfcGxvdChjb25mdXNlX21hdHJpeDEsMDo1LDA6NCxzZXNzaW9uID0gImRzMHRvZHMyIikKCklkZW50cyhkczIpIDwtIGZhY3RvcihkczJfcmVzLGxldmVscyA9IGMoMDo1KSkKdW1hcHBsb3QoZHMyKQoKYGBgCgpgYGB7cn0KZW1iZWRkaW5nIDwtIEZldGNoRGF0YShvYmplY3QgPSBkczIsIHZhcnMgPSBjKCJVTUFQXzEiLCAiVU1BUF8yIikpCmVtYmVkZGluZyA8LSBjYmluZChlbWJlZGRpbmcsIHQocHJlZGljdF9wcm9wX2RzMikpCgpnZ29iaiA8LSBnZ3Bsb3QoKSArCiAgZ2VvbV9wb2ludChkYXRhID0gZW1iZWRkaW5nW2VtYmVkZGluZyRgMGA+MC4xLF0sIAogICAgICAgICAgICAgYWVzKHggPSBVTUFQXzEsIHkgPSBVTUFQXzIsIGNvbG9yID0gYDBgKSwgc2hhcGU9MTYsIHNpemUgPSAyLCBhbHBoYT0wLjUpICsgCiAgc2NhbGVfY29sb3JfZ3JhZGllbnQoJzAnLCBsb3cgPSAiI0ZGRkZGRjAwIiwgaGlnaCA9ICIjNmRjMGE2IikgKwogIG5ld19zY2FsZSgiY29sb3IiKSArCiAgICBnZW9tX3BvaW50KGRhdGEgPSBlbWJlZGRpbmdbZW1iZWRkaW5nJGAxYD4wLjEsXSwgCiAgICAgICAgICAgICBhZXMoeCA9IFVNQVBfMSwgeSA9IFVNQVBfMiwgY29sb3IgPSBgMWApLHNoYXBlPTE2LCBzaXplID0gMiwgYWxwaGE9MC41KSArIAogIHNjYWxlX2NvbG9yX2dyYWRpZW50KCcxJywgbG93ID0gIiNGRkZGRkYwMCIsIGhpZ2ggPSAiI2UyYjM5OCIpICsKICAgbmV3X3NjYWxlKCJjb2xvciIpICsKICAgIGdlb21fcG9pbnQoZGF0YSA9IGVtYmVkZGluZ1tlbWJlZGRpbmckYDJgPjAuMSxdLCAKICAgICAgICAgICAgIGFlcyh4ID0gVU1BUF8xLCB5ID0gVU1BUF8yLCBjb2xvciA9IGAyYCksc2hhcGU9MTYsIHNpemUgPSAyLCBhbHBoYT0wLjUpICsgCiAgc2NhbGVfY29sb3JfZ3JhZGllbnQoJzInLCBsb3cgPSAiI0ZGRkZGRjAwIiwgaGlnaCA9ICIjZTJhMmNhIikgKwogICAgIG5ld19zY2FsZSgiY29sb3IiKSArCiAgICBnZW9tX3BvaW50KGRhdGEgPSBlbWJlZGRpbmdbZW1iZWRkaW5nJGAzYD4wLjEsXSwgCiAgICAgICAgICAgICBhZXMoeCA9IFVNQVBfMSwgeSA9IFVNQVBfMiwgY29sb3IgPSBgM2ApLHNoYXBlPTE2LCBzaXplID0gMiwgYWxwaGE9MC41KSArIAogIHNjYWxlX2NvbG9yX2dyYWRpZW50KCczJywgbG93ID0gIiNGRkZGRkYwMCIsIGhpZ2ggPSAiI2QxZWJhOCIpICsKICAgICBuZXdfc2NhbGUoImNvbG9yIikgKwogICAgZ2VvbV9wb2ludChkYXRhID0gZW1iZWRkaW5nW2VtYmVkZGluZyRgNGA+MC4xLF0sIAogICAgICAgICAgICAgYWVzKHggPSBVTUFQXzEsIHkgPSBVTUFQXzIsIGNvbG9yID0gYDRgKSxzaGFwZT0xNiwgc2l6ZSA9IDIsIGFscGhhPTAuNSkgKyAKICBzY2FsZV9jb2xvcl9ncmFkaWVudCgnNCcsIGxvdyA9ICIjRkZGRkZGMDAiLCBoaWdoID0gIiNiMWQ2ZmIiKSArCiAgICAgbmV3X3NjYWxlKCJjb2xvciIpICsKICAgIGdlb21fcG9pbnQoZGF0YSA9IGVtYmVkZGluZ1tlbWJlZGRpbmckYDVgPjAuMSxdLCAKICAgICAgICAgICAgIGFlcyh4ID0gVU1BUF8xLCB5ID0gVU1BUF8yLCBjb2xvciA9IGA1YCksc2hhcGU9MTYsIHNpemUgPSAyLCBhbHBoYT0wLjUpICsgCiAgc2NhbGVfY29sb3JfZ3JhZGllbnQoJzUnLCBsb3cgPSAiI0ZGRkZGRjAwIiwgaGlnaCA9ICIjZmQ5OTk5IikgKwogICAgICAgIHhsYWIoIlVNQVAgMSIpICsgeWxhYigiVU1BUCAyIikgICsKICAgICAgICB0aGVtZShheGlzLmxpbmUgPSBlbGVtZW50X2xpbmUoYXJyb3cgPSBhcnJvdyhsZW5ndGggPSB1bml0KDAuMiwgImNtIikpKSkgKwogICAgICAgIHNjYWxlX3lfY29udGludW91cyhicmVha3MgPSBOVUxMKSArCiAgICAgICAgc2NhbGVfeF9jb250aW51b3VzKGJyZWFrcyA9IE5VTEwpICsgCiAgdGhlbWUocGFuZWwuYmFja2dyb3VuZCA9IGVsZW1lbnRfYmxhbmsoKSwgcGFuZWwuZ3JpZCA9IGVsZW1lbnRfYmxhbmsoKSwgbGVnZW5kLnBvc2l0aW9uID0gImJvdHRvbSIpCmdnc2F2ZSgiZHMwdG9kczJ1bWFwLnN2ZyIsZGV2aWNlID0gc3ZnLHBsb3QgPSBnZ29iaixoZWlnaHQgPSA4LHdpZHRoID0gOCkKYGBgCgojIyBkczAgLT4gZHMxCmBgYHtyfQpJZGVudHMoZHMxKSA8LSBkczEkc2V1cmF0X2NsdXN0ZXJzCnRlbXAgPC0gZ2V0X2RhdGFfdGFibGUoZHMxLCBoaWdodmFyID0gRiwgdHlwZSA9ICJkYXRhIikKZHMxX2RhdGEgPC0gbWF0cml4KGRhdGE9MCwgbnJvdyA9IGxlbmd0aChyb3duYW1lcyhkczBfZGF0YSkpLCBuY29sID0gbGVuZ3RoKGNvbG5hbWVzKHRlbXApKSwgCiAgICAgICAgICAgICAgICAgICBieXJvdyA9IEZBTFNFLCBkaW1uYW1lcyA9IGxpc3Qocm93bmFtZXMoZHMwX2RhdGEpLGNvbG5hbWVzKHRlbXApKSkKZm9yKGkgaW4gaW50ZXJzZWN0KHJvd25hbWVzKGRzMV9kYXRhKSwgcm93bmFtZXModGVtcCkpKXsKICBkczFfZGF0YVtpLF0gPC0gdGVtcFtpLF0KfQpybSh0ZW1wKQpkczFfbGFiZWwgPC0gYXMubnVtZXJpYyhhcy5jaGFyYWN0ZXIoSWRlbnRzKGRzMSkpKQpjb2xuYW1lcyhkczFfZGF0YSkgPC0gTlVMTApkczFfdGVzdF9kYXRhIDwtIGxpc3QoZGF0YSA9IHQoYXMoZHMxX2RhdGEsImRnQ01hdHJpeCIpKSwgbGFiZWwgPSBkczFfbGFiZWwpCmRzMV90ZXN0IDwtIHhnYi5ETWF0cml4KGRhdGEgPSBkczFfdGVzdF9kYXRhJGRhdGEsbGFiZWwgPSBkczFfdGVzdF9kYXRhJGxhYmVsKQoKI+mihOa1i+e7k+aenAoKcHJlZGljdF9kczFfdGVzdCA8LSBwcmVkaWN0KGJzdF9tb2RlbCwgbmV3ZGF0YSA9IGRzMV90ZXN0KQoKcHJlZGljdF9wcm9wX2RzMSA8LSBtYXRyaXgoZGF0YT1wcmVkaWN0X2RzMV90ZXN0LCBucm93ID0gYnN0X21vZGVsW1sicGFyYW1zIl1dW1sibnVtX2NsYXNzIl1dLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgbmNvbCA9IG5jb2woZHMxKSwgYnlyb3cgPSBGQUxTRSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgIGRpbW5hbWVzID0gbGlzdChjKDA6KGJzdF9tb2RlbFtbInBhcmFtcyJdXVtbIm51bV9jbGFzcyJdXS0xKSksY29sbmFtZXMoZHMxKSkpCgojIyDlvpfliLDliIbnvqTnu5PmnpwKZHMxX3JlcyA8LSBhcHBseShwcmVkaWN0X3Byb3BfZHMxLDIsZnVuYyxyb3duYW1lcyhwcmVkaWN0X3Byb3BfZHMxKSkKSWRlbnRzKGRzMSkgPC0gZmFjdG9yKGRzMV9yZXMsbGV2ZWxzID0gYygwOjUpKQp1bWFwcGxvdChkczEpCgpjb25mdXNlX21hdHJpeCA8LSB0YWJsZShkczFfdGVzdF9kYXRhJGxhYmVsLCBkczFfcmVzLCBkbm49YygidHJ1ZSIsInByZSIpKQpzYW5rZXlfcGxvdChjb25mdXNlX21hdHJpeCxjKDA6NCksYygwOjQpLHNlc3Npb24gPSAiZHMwdG9kczEiKQpgYGAKCmBgYHtyfQplbWJlZGRpbmcgPC0gRmV0Y2hEYXRhKG9iamVjdCA9IGRzMSwgdmFycyA9IGMoIlVNQVBfMSIsICJVTUFQXzIiKSkKZW1iZWRkaW5nIDwtIGNiaW5kKGVtYmVkZGluZywgdChwcmVkaWN0X3Byb3BfZHMxKSkKCmdnb2JqIDwtIGdncGxvdCgpICsKICBnZW9tX3BvaW50KGRhdGEgPSBlbWJlZGRpbmdbZW1iZWRkaW5nJGAwYD4wLjEsXSwgCiAgICAgICAgICAgICBhZXMoeCA9IFVNQVBfMSwgeSA9IFVNQVBfMiwgY29sb3IgPSBgMGApLCBzaGFwZT0xNiwgc2l6ZSA9IDIsIGFscGhhPTAuNSkgKyAKICBzY2FsZV9jb2xvcl9ncmFkaWVudCgnMCcsIGxvdyA9ICIjRkZGRkZGMDAiLCBoaWdoID0gIiM2ZGMwYTYiKSArCiAgbmV3X3NjYWxlKCJjb2xvciIpICsKICAgIGdlb21fcG9pbnQoZGF0YSA9IGVtYmVkZGluZ1tlbWJlZGRpbmckYDFgPjAuMSxdLCAKICAgICAgICAgICAgIGFlcyh4ID0gVU1BUF8xLCB5ID0gVU1BUF8yLCBjb2xvciA9IGAxYCksc2hhcGU9MTYsIHNpemUgPSAyLCBhbHBoYT0wLjUpICsgCiAgc2NhbGVfY29sb3JfZ3JhZGllbnQoJzEnLCBsb3cgPSAiI0ZGRkZGRjAwIiwgaGlnaCA9ICIjZTJiMzk4IikgKwogICBuZXdfc2NhbGUoImNvbG9yIikgKwogICAgZ2VvbV9wb2ludChkYXRhID0gZW1iZWRkaW5nW2VtYmVkZGluZyRgMmA+MC4xLF0sIAogICAgICAgICAgICAgYWVzKHggPSBVTUFQXzEsIHkgPSBVTUFQXzIsIGNvbG9yID0gYDJgKSxzaGFwZT0xNiwgc2l6ZSA9IDIsIGFscGhhPTAuNSkgKyAKICBzY2FsZV9jb2xvcl9ncmFkaWVudCgnMicsIGxvdyA9ICIjRkZGRkZGMDAiLCBoaWdoID0gIiNlMmEyY2EiKSArCiAgICAgbmV3X3NjYWxlKCJjb2xvciIpICsKICAgIGdlb21fcG9pbnQoZGF0YSA9IGVtYmVkZGluZ1tlbWJlZGRpbmckYDNgPjAuMSxdLCAKICAgICAgICAgICAgIGFlcyh4ID0gVU1BUF8xLCB5ID0gVU1BUF8yLCBjb2xvciA9IGAzYCksc2hhcGU9MTYsIHNpemUgPSAyLCBhbHBoYT0wLjUpICsgCiAgc2NhbGVfY29sb3JfZ3JhZGllbnQoJzMnLCBsb3cgPSAiI0ZGRkZGRjAwIiwgaGlnaCA9ICIjZDFlYmE4IikgKwogICAgIG5ld19zY2FsZSgiY29sb3IiKSArCiAgICBnZW9tX3BvaW50KGRhdGEgPSBlbWJlZGRpbmdbZW1iZWRkaW5nJGA0YD4wLjEsXSwgCiAgICAgICAgICAgICBhZXMoeCA9IFVNQVBfMSwgeSA9IFVNQVBfMiwgY29sb3IgPSBgNGApLHNoYXBlPTE2LCBzaXplID0gMiwgYWxwaGE9MC41KSArIAogIHNjYWxlX2NvbG9yX2dyYWRpZW50KCc0JywgbG93ID0gIiNGRkZGRkYwMCIsIGhpZ2ggPSAiI2IxZDZmYiIpICsKICAgICBuZXdfc2NhbGUoImNvbG9yIikgKwogICAgZ2VvbV9wb2ludChkYXRhID0gZW1iZWRkaW5nW2VtYmVkZGluZyRgNWA+MC4xLF0sIAogICAgICAgICAgICAgYWVzKHggPSBVTUFQXzEsIHkgPSBVTUFQXzIsIGNvbG9yID0gYDVgKSxzaGFwZT0xNiwgc2l6ZSA9IDIsIGFscGhhPTAuNSkgKyAKICBzY2FsZV9jb2xvcl9ncmFkaWVudCgnNScsIGxvdyA9ICIjRkZGRkZGMDAiLCBoaWdoID0gIiNmZDk5OTkiKSArCiAgICAgICAgeGxhYigiVU1BUCAxIikgKyB5bGFiKCJVTUFQIDIiKSAgKwogICAgICAgIHRoZW1lKGF4aXMubGluZSA9IGVsZW1lbnRfbGluZShhcnJvdyA9IGFycm93KGxlbmd0aCA9IHVuaXQoMC4yLCAiY20iKSkpKSArCiAgICAgICAgc2NhbGVfeV9jb250aW51b3VzKGJyZWFrcyA9IE5VTEwpICsKICAgICAgICBzY2FsZV94X2NvbnRpbnVvdXMoYnJlYWtzID0gTlVMTCkgKyAKICB0aGVtZShwYW5lbC5iYWNrZ3JvdW5kID0gZWxlbWVudF9ibGFuaygpLCBwYW5lbC5ncmlkID0gZWxlbWVudF9ibGFuaygpLCBsZWdlbmQucG9zaXRpb24gPSAiYm90dG9tIikKZ2dzYXZlKCJkczB0b2RzMXVtYXAuc3ZnIixkZXZpY2UgPSBzdmcscGxvdCA9IGdnb2JqLGhlaWdodCA9IDgsd2lkdGggPSA4KQpgYGAKCgoKIyNseW0KYGBge3J9CgpgYGAKCgojIyBBUkkg5ZKM6IGa57G75pWw55qE5YWz57O7CkFkZCBhIG5ldyBjaHVuayBieSBjbGlja2luZyB0aGUgKkluc2VydCBDaHVuayogYnV0dG9uIG9uIHRoZSB0b29sYmFyIG9yIGJ5IHByZXNzaW5nICpDdHJsK0FsdCtJKi4KCldoZW4geW91IHNhdmUgdGhlIG5vdGVib29rLCBhbiBIVE1MIGZpbGUgY29udGFpbmluZyB0aGUgY29kZSBhbmQgb3V0cHV0IHdpbGwgYmUgc2F2ZWQgYWxvbmdzaWRlIGl0IChjbGljayB0aGUgKlByZXZpZXcqIGJ1dHRvbiBvciBwcmVzcyAqQ3RybCtTaGlmdCtLKiB0byBwcmV2aWV3IHRoZSBIVE1MIGZpbGUpLgoKVGhlIHByZXZpZXcgc2hvd3MgeW91IGEgcmVuZGVyZWQgSFRNTCBjb3B5IG9mIHRoZSBjb250ZW50cyBvZiB0aGUgZWRpdG9yLiBDb25zZXF1ZW50bHksIHVubGlrZSAqS25pdCosICpQcmV2aWV3KiBkb2VzIG5vdCBydW4gYW55IFIgY29kZSBjaHVua3MuIEluc3RlYWQsIHRoZSBvdXRwdXQgb2YgdGhlIGNodW5rIHdoZW4gaXQgd2FzIGxhc3QgcnVuIGluIHRoZSBlZGl0b3IgaXMgZGlzcGxheWVkLgo=